Skip to content

feat(oracles/pyth): add pinocchio example - #707

Open
MarkFeder wants to merge 2 commits into
solana-foundation:mainfrom
MarkFeder:oracles-pyth-pinocchio
Open

feat(oracles/pyth): add pinocchio example#707
MarkFeder wants to merge 2 commits into
solana-foundation:mainfrom
MarkFeder:oracles-pyth-pinocchio

Conversation

@MarkFeder

Copy link
Copy Markdown
Contributor

What

Adds a Pinocchio implementation of the Pyth oracle example (the first pinocchio example under oracles/), alongside the existing anchor version. A single read_price instruction reads a Pyth pull-oracle PriceUpdateV2 account and logs its price fields.

How it works

There is no Pyth SDK for Pinocchio, so the account is parsed by hand:

  1. Validate the account's owner is the Pyth receiver program (rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ) and its 8-byte anchor discriminator — so the program never trusts the price data of an arbitrary account (what anchor's Account<PriceUpdateV2> does for free).
  2. Locate the PriceFeedMessage: it follows the discriminator (8), write authority (32), and the VerificationLevel. That enum is variable-size (Full = 1 byte, Partial { num_signatures } = 2), so the message offset is computed from it.
  3. Read price (i64), conf (u64), exponent (i32), and publish_time (i64) by little-endian byte offset and log them.

Test

litesvm + @solana/kit. Since litesvm doesn't bundle Pyth, the test constructs a mock PriceUpdateV2 account with known values and injects it via setAccount, then:

  • asserts the program logs the parsed price / conf / exponent / publish_time, and
  • asserts a second account not owned by the Pyth receiver program is rejected.
Pyth (Pinocchio)
  ✔ Reads the price from a Pyth price update account
  ✔ Rejects an account not owned by the Pyth receiver program
2 passing

Verified locally: cargo build-sbf, the litesvm tests, tsc --noEmit, Prettier, cargo fmt --check, Clippy, and pnpm install --frozen-lockfile all clean. (The deploy script uses the *.so glob per #702.)


AI use: I chose the approach (hand-parsing the PriceUpdateV2 layout, validating owner + discriminator, the mock-account test) and verified the layout and discriminator against the pyth-solana-receiver-sdk / pythnet-sdk sources; implementation and tests were written with Claude Code and reviewed by me.

@MarkFeder
MarkFeder requested a review from dev-jodee as a code owner August 30, 2026 00:07
@greptile-apps

greptile-apps Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds a Pinocchio implementation of the Pyth oracle example.

  • Validates the Pyth receiver owner and PriceUpdateV2 discriminator before manually decoding price fields.
  • Handles both Full and Partial verification-level layouts and rejects unknown discriminants.
  • Adds LiteSVM coverage using synthetic accounts and a captured mainnet fixture.
  • Adds the program to the Cargo workspace and supplies package, build, deployment, and fixture-preparation configuration.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported malformed verification-level acceptance is fixed by rejecting every discriminant other than the two supported variants before reading price fields.

Important Files Changed

Filename Overview
oracles/pyth/pinocchio/program/src/instructions/read_price.rs Validates account ownership, discriminator, and verification-level discriminant before safely reading the Pyth price fields with bounds checks.
oracles/pyth/pinocchio/tests/test.ts Covers full and partial verification layouts, malformed verification levels, wrong ownership, and parsing of a captured mainnet account.
oracles/pyth/pinocchio/program/src/processor.rs Routes the example’s sole instruction directly to the price-reading handler.
oracles/pyth/pinocchio/prepare.mjs Prepares the captured Pyth account fixture used by the integration test.
oracles/pyth/pinocchio/package.json Defines the Pinocchio example’s build, test, deployment, and fixture-preparation scripts and dependencies.

Reviews (3): Last reviewed commit: "test(oracles/pyth): cover the real feed ..." | Re-trigger Greptile

Comment thread oracles/pyth/pinocchio/program/src/instructions/read_price.rs Outdated
@MarkFeder
MarkFeder force-pushed the oracles-pyth-pinocchio branch from b89fd70 to d5c713d Compare August 30, 2026 00:12
Adds a Pinocchio implementation of the Pyth oracle example, the first pinocchio
example under oracles/. The single read_price instruction reads a Pyth pull
oracle PriceUpdateV2 account and logs its price fields.

There is no Pyth SDK for Pinocchio, so the account is parsed by hand: the
program checks the account's owner (the Pyth receiver program) and the anchor
account discriminator before trusting the data, then reads the price, confidence,
exponent, and publish time by byte offset. The account's VerificationLevel is a
variable-size Borsh enum, so the price message offset is computed from it.

The litesvm test injects a mock PriceUpdateV2 account with known values via
setAccount and asserts the program logs the parsed fields, plus a second case
that a non-Pyth-owned account is rejected.
@MarkFeder
MarkFeder force-pushed the oracles-pyth-pinocchio branch from d5c713d to 0e328ad Compare August 30, 2026 00:12
…offset

The mock accounts are built by the same code that the assertions trust, so
they stay self-consistent even if the layout drifts from what Pyth writes.
Adds prepare.mjs, which dumps the mainnet SOL/USD price update, and a test
that decodes the price out of those bytes in TypeScript and asserts the
program logs the same value.

`encodePriceUpdate` also wrote the price message at the `Full` offset
whatever verification level it was given, so a `Partial` account it built
was internally inconsistent and the program's dynamic offset went
untested. It now honours the level, and a new test reads back a partially
verified update whose message really does sit one byte later.
@MarkFeder

Copy link
Copy Markdown
Contributor Author

Pushed two test additions. No program changes.

A real mainnet feed, not just mocks. encodePriceUpdate builds the account with the same offsets the assertions trust, so it stays self-consistent even if the layout drifts from what Pyth actually writes — a wrong offset in both places passes. prepare.mjs now dumps the mainnet SOL/USD update (7UVimffx…) into tests/fixtures, and the new test loads it owner-and-all, decodes the price and exponent out of those bytes in TypeScript, and asserts the program logs the same values.

The partial-verification offset was untested. encodePriceUpdate wrote the price message at the Full offset regardless of the level it was given, so an account it built with tag Partial was internally inconsistent — the existing third test uses an unknown tag (2) instead, which the program rejects before it ever reads the message. It now sizes the enum properly, and a new test reads back a partially verified update whose message really does sit one byte later. That is the one non-obvious branch in the parser.

Pyth (Pinocchio)
  ✔ Reads the price from a Pyth price update account
  ✔ Rejects an account not owned by the Pyth receiver program
  ✔ Rejects an account with an unknown verification level
  ✔ Reads a partially verified update, whose message sits one byte later
  ✔ Reads the real SOL/USD feed dumped from mainnet
5 passing

prepare.mjs uses solana account -um rather than solana config set -um, per #720. Verified: build-and-test, tsc --noEmit, pnpm install --frozen-lockfile (which runs the new postinstall), and Prettier all clean.

@MarkFeder

Copy link
Copy Markdown
Contributor Author

@amilz could you take a look at this one when you get a chance?

No open review threads left on it, so it is ready for maintainer review. It is one of 23 open Pinocchio ports I have up — they are independent and self-contained, so they can be reviewed and merged in any order: https://github.com/solana-developers/program-examples/pulls/MarkFeder

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant